分布式缓存和本地缓存的一致性在项目中的解决方案

您所在的位置:网站首页 redis 集群怎么保证数据一致性 分布式缓存和本地缓存的一致性在项目中的解决方案

分布式缓存和本地缓存的一致性在项目中的解决方案

2024-07-06 14:53| 来源: 网络整理| 查看: 265

项目背景

项目结构如下:

A和B分别是两个web项目,且B项目通过多个节点构成一个分布式集群,外部http请求会通过nginx转发到B项目。

在这个前提下,我们先看如下业务流程,流程清楚了之后再谈谈解决方案。

1.放入缓存

放入缓存的核心流程是:

A项目修改或者创建采集模型时会通过http接口调用B项目。 B项目将采集模型信息既放入redis,又放入本地map缓存:缓存的key为模型的token,value为模型的所有采集字段。

除了redis,还在本地缓存中放一份数据的原因是:

当请求过来时,可以查询本地缓存,而不与redis交互。本地查询性能肯定快于redis查询。

核心代码如下

private ConcurrentHashMap tokenAndColumns = new ConcurrentHashMap(); ...... ...... public static Result operatorCollectModel(CollectModel collectModel){ if("add".equals(collectModel.getOperator()) || "update".equals(collectModel.getOperator()) ){ //放入redis redisTemplate.opsForHash().putAll(collectModel.getToken(), collectModel.getColumns()); //放入本地map tokenAndColumns.put(collectModel.getToken(), collectModel.getColumns()); }else if("delete".equals(collectModel.getOperator())){ redisTemplate.delete(collectModel.getToken()); tokenAndColumns.remove(collectModel.getToken()); } return new Result(true); } 2.查询缓存

查询缓存的核心流程如下:

外部应用请求B项目,B项目根据请求带过来的token参数,查询本地缓存,如果本地有数据则无需再查询redis;如果本地没有,即map中不存在这个key,则查询redis缓存,并将查询结果放入本地缓存。

核心代码如下

//先查询本地 Map columns = tokenAndColumns.get(accessToken); if(CollectionUtils.isEmpty(columns)){ //查询redis columns = redisTemplate.opsForHash().entries(accessToken); //放入本地缓存 if(!CollectionUtils.isEmpty(columns)){ tokenAndColumns.putIfAbsent(accessToken, columns); } }

因此,当第一次请求触发后,后台会将redis数据放入本地map,后续同一个节点的请求过来时,直接查询本地缓存。

上述设计确实可以提高查询性能,但是在项目运行过程中发现了如下业务问题:

通过日志发现,B项目从本地缓存获取到的采集模型信息和实际的采集模型信息不一致,但是从redis中获取的采集模型信息没有任何问题。

问题分析

开头提到B项目存在多个节点,那么下面的操作步骤就可能导致上述问题:

A项目第一次创建采集模型时,通过http调用B项目,后者将信息放入了redis和本地缓存 。之后A项目修改了模型信息,比如添加了新的采集字段。由于B项目有多个节点,A项目的http请求通过nginx转发到了B1项目而不是B项目,于是B1项目将信息放入了redis和本地缓存。此时,B项目本地缓存的key虽然存在,但是value代表的采集模型信息不是最新的,导致B从本地缓存查询到的数据有误,而redis数据始终是正确的。 解决

通过分析可知,根本原因是本地缓存与redis缓存不一致,所以解决方法是:

写一个定时任务,定时同步redis数据到本地缓存。

核心代码如下

@Scheduled(cron = "0 */1 * * * ?") public void syncRedisToLocal(){ Set accessTokens = tokenAndColumns.keySet().stream().collect(Collectors.toSet()); accessTokens.parallelStream().forEach(accessToken->{ //获取redis数据 Map map = redisTemplate.opsForHash().entries(accessToken); //刷新本地缓存 tokenAndColumns.put(accessToken, map); }); }

但这样有个弊端,假如后期修改了模型字段,在定时任务触发前,本地缓存数据依旧是旧的。 不过,虽然短期内redis和本地缓存数据不一致,但最终肯定是一致的。这个就看业务取舍了。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3